package com.wechat.user.service;

import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.wechat.common.id.core.IdGenerator;
import com.wechat.common.web.exception.BusinessException;
import com.wechat.common.web.result.ResultCode;
import com.wechat.user.entity.User;
import com.wechat.user.mapper.UserMapper;
import com.wechat.user.request.RegisterRequest;
import com.wechat.user.request.RegisterSmsRequest;
import com.wechat.user.util.PasswordEncoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.Objects;

@Slf4j
@Service
public class RegisterService {

    /**
     * 验证码前缀
     */
    private static final String SMS_CODE_PREFIX = "sms:register:code:";
    /**
     * 验证码错误次数前缀
     */
    private static final String SMS_ERROR_COUNT_PREFIX = "sms:register:error:";
    /**
     * 最大验证码错误次数
     */
    private static final int MAX_ERROR_COUNT = 5;
    /**
     * 验证码错误锁定时间（分钟）
     */
    private static final int LOCK_TIME_MINUTES = 30;
    /**
     * 验证码过期时间
     */
    private static final int CODE_EXPIRE_MINUTES = 2;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private IdGenerator idGenerator;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Transactional(rollbackFor = Exception.class)
    public void register(RegisterRequest request) {
        String phone = request.getPhone();
        String userName = request.getUserName();
        String password = request.getPassword();
        String code = request.getCode();
        log.debug("用户注册开始，手机号：{}", phone);
        // 1. 判断手机号是否注册
        checkPhoneExists(phone);
        // 2. 比对验证码
        verifyCode(phone, code);
        // 3. 比对验证码错误次数
        checkErrorCount(phone);
        // 4. 用户验证通过，生成用户基本信息
        User user = buildUser(userName, phone, password);
        // 5. 插入用户（数据库中）
        insertUser(user);
        // 6. 清理验证码相关缓存
        cleanupCache(phone);
        log.debug("用户注册成功，用户ID：{}，手机号：{}", user.getUserId(), phone);
    }

    /**
     * 检查手机号是否已注册
     */
    private void checkPhoneExists(String phone) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getPhone, phone);
        Long count = userMapper.selectCount(queryWrapper);
        if (count > 0) {
            log.warn("手机号已注册：{}", phone);
            throw new BusinessException(ResultCode.PHONE_ALREADY_REGISTERED);
        }
    }

    /**
     * 验证短信验证码
     */
    private void verifyCode(String phone, String inputCode) {
        String cacheKey = SMS_CODE_PREFIX + phone;
        String cachedCode = stringRedisTemplate.opsForValue().get(cacheKey);
        if (!StringUtils.hasText(cachedCode)) {
            log.warn("验证码不存在或已过期，手机号：{}", phone);
            throw new BusinessException(ResultCode.VERIFICATION_CODE_EXPIRED);
        }

        if (!Objects.equals(cachedCode, inputCode)) {
            // 验证码错误，记录错误次数
            incrementErrorCount(phone);
            log.warn("验证码错误，手机号：{}，输入验证码：{}", phone, inputCode);
            throw new BusinessException(ResultCode.VERIFICATION_CODE_ERROR);
        }
    }

    /**
     * 检查验证码错误次数
     */
    private void checkErrorCount(String phone) {
        String errorKey = SMS_ERROR_COUNT_PREFIX + phone;
        String redisStr = stringRedisTemplate.opsForValue().get(errorKey);
        if (redisStr == null) {
            return;
        }
        int errorCount = Integer.parseInt(redisStr);
        if (errorCount >= MAX_ERROR_COUNT) {
            log.warn("验证码错误次数过多，手机号：{}，错误次数：{}", phone, errorCount);
            throw new BusinessException(ResultCode.VERIFICATION_CODE_ERROR_TOO_MUCH);
        }
    }

    /**
     * 增加验证码错误次数
     */
    private void incrementErrorCount(String phone) {
        String errorKey = SMS_ERROR_COUNT_PREFIX + phone;
        String redisStr = stringRedisTemplate.opsForValue().get(errorKey);
        if (redisStr == null) {
            redisStr = "0";
        }
        int currentCount = Integer.parseInt(redisStr);
        int newCount = currentCount + 1;
        // 设置错误次数，30分钟过期
        stringRedisTemplate.opsForValue().set(errorKey, String.valueOf(newCount), Duration.ofMinutes(LOCK_TIME_MINUTES));
        log.debug("记录验证码错误次数，手机号：{}，当前错误次数：{}", phone, newCount);
    }

    /**
     * 构建用户对象
     */
    private User buildUser(String userName, String phone, String password) {
        User user = new User();
        user.setUserId(idGenerator.generateId());
        user.setUserName(userName);
        user.setPhone(phone);
        user.setPassword(passwordEncoder.encode(password));
        // 设置默认值
        user.setAvatar("http://118.25.77.201:9000/infinitec-chat/infinitechat_default_avatar.png");
        user.setGender(User.Gender.SECRET.getCode());
        user.setStatus(User.Status.NORMAL.getCode());
        return user;
    }

    /**
     * 插入用户到数据库
     */
    private void insertUser(User user) {
        int result = userMapper.insert(user);
        if (result != 1) {
            throw new BusinessException(ResultCode.DATABASE_ERROR);
        }
    }

    /**
     * 清理缓存
     */
    private void cleanupCache(String phone) {
        // 清理验证码缓存
        String codeKey = SMS_CODE_PREFIX + phone;
        stringRedisTemplate.delete(codeKey);
        // 清理错误次数缓存
        String errorKey = SMS_ERROR_COUNT_PREFIX + phone;
        stringRedisTemplate.delete(errorKey);
        log.debug("清理缓存成功，手机号：{}", phone);
    }

    public void sms(RegisterSmsRequest request) {
        String cacheKey = SMS_CODE_PREFIX + request.getPhone();
        String redisStr = stringRedisTemplate.opsForValue().get(cacheKey);
        if (redisStr != null) {
            throw new BusinessException(ResultCode.VERIFICATION_CODE_SEND_TOO_FREQUENT);
        }
        String code = RandomUtil.randomNumbers(6);
        // TODO: 短信服务发送验证码
        log.debug("{} 验证码 {}", request.getPhone(), code);
        stringRedisTemplate.opsForValue().set(cacheKey, code, Duration.ofMinutes(CODE_EXPIRE_MINUTES));
    }
}
